package com.googlecode.project4uz.encoder;

import java.io.OutputStream;

import org.openrdf.rio.RDFFormat;
import org.openrdf.rio.RDFHandlerException;
import org.openrdf.rio.Rio;

/**
 * Wykorzystanie biblioteki Rio do zapisu plikow RDF.
 * @author Daniel Kur
 */
class RioWriter extends RioWriterHelper {	
	/**
	 * Licznik zmiennych typu referencyjnego.
	 */
	private transient int id = 0;

	/**
	 * Tworzy obiekt writera obiektow do formatu RDF.
	 * @param format - format RDF jakim zapisujemy do strumienia.
	 * @param output - strumien wyjsciowy dla wyniku.
	 */
	RioWriter(final RDFFormat format, final OutputStream output) {
		super(Rio.createWriter(format, output));
		this.open();
	}

	/**
	 * Pozwala na zapis referencji do strumienia w formacie RDF.
	 * @author Daniel Kur
	 */
	class Reference {
		/**
		 * Unikalny identyfikator dla referencji. 
		 */
		private final String ID;
		
		/**
		 * Reprezentuje obiekt referencyjny w formacie RDF.
		 * @param type - nazwa typu referencyjnego.
		 */
		Reference(final String type) {
			this.ID = type + "#ID" + id++;
		}
				
		/**
		 * Reprezentuje obiekt referencyjny w formacie RDF.
		 * @param reference - istniejacy typ referencyjny.
		 */
		Reference(final char[] reference) {
			this.ID = new String(reference);
		}
		
		/**
		 * Pobiera wartosc identyfikatora referencji.
		 * @return unikalny identyfikator referencji.
		 */
		String getID() {
			return ID;
		}

		/**
		 * Dodaje zmienna primitywna do referencji.
		 * @param nazme - nazwa zmiennej primitywnej.
		 * @param type - typ zmiennej primitywnej.
		 * @param value - wartosc zmiennej primitywnej.
		 */
		void writePrimitive(final String name, final String type, final String value) {
			addPrimitive(ID, type, name, value);
		}

		/**
		 * Dodaje prywatne pole serialVersionUID z serializowanej klasy.
		 * @param value - wartosc pola serialVersionUID.
		 */
		void writeSerial(final String value) {
			addPrimitive(ID, "serial", "serialVersionUID", value);
		}

		/**
		 * Dodaje zmienna referencyjna do referencji.
		 * @param name - nazwa referencji.
		 * @param type - nazwa typu referencji.
		 * @return zmienna referencyjna w reprezentacji RDF.
		 */
		Reference writeReference(final String name, final String type) {
			Reference ref = new Reference(type);
			addReference(ID, name, "reference", ref.ID);
			return ref;
		}

		/**
		 * Dodaje wskaznik na juz istniejaca referencje.
		 * @param name - nazwa referencji.
		 * @param identifier - id referencja na ktora ma wskazywac.
		 */
		void writePointer(final String name, final String identifier) {
			addReference(ID, name, "reference", identifier);
		}

		/**
		 * Dodaje pusta referencje.
		 * @param name - nazwa referencji.
		 * @param type - nazwa typu referencji.
		 */
		void writeNull(final String name, final String type) {
			addReference(ID, name, "reference", "null");
		}

		/**
		 * Dodaje zmienna tablicowa do referencji.
		 * @param name - nazwa zmiennej tablicowej.
		 * @param type - typ zmiennej tablicowej.
		 * @param size - ilosc elementow w tablicy.
		 * @param dimension - ilosc wymiarow tablicy.
		 * @return zmienna tablicowa w reprezentacji RDF.
		 */
		Array writeArray(final String name, final String type, int size, int dimension) {
			Array array = new Array(type, size, dimension);
			addArray(ID, name, array.ID);
			return array;
		}

		/**
		 * Dodaje kolekcje do referencji.
		 * @param type - typ kolekcji.
		 * @param generic - typ generyczny kolekcji.
		 * @return kolekcja w reprezentacji RDF. 
		 */
		Collection writeCollection(final String name, final String type, final String generic) {
			Collection collection = new Collection(type, generic);
			addCollection(ID, name, collection.ID);
			return collection;
		}

		/**
		 * Dodaje mape do referencji.
		 * @param type - typ mapy.
		 * @param key - typ generyczny klucza mapy.
		 * @param value - typ generyczny wartosci mapy.
		 * @return mapa w reprezentacji RDF.
		 */
		Map writeMap(final String name, final String type, final String key, final String value) {
			Map map = new Map(type, key, value);
			addMap(ID, name, map.ID);
			return map;
		}

		/**
		 * Pozwala na zapis tablic do strumienia w formacie RDF.
		 * @author Daniel Kur
		 */
		class Array {
			/**
			 * Unikalny identyfikator dla tablicy. 
			 */
			private final String ID;
			
			/**
			 * Typ zmiennej tablicowej. 
			 */
			private final String TYPE;
			
			/**
			 * Licznik ilosci wymiarow w tablicy. 
			 */
			private transient int dimension;
			
			/**
			 * Licznik ilosci elementow w tablicy. 
			 */
			private transient int size;

			/**
			 * Reprezentuje obiekt tablicowy w formacie RDF.
			 * @param type - typ zmiennej tablicowej.
			 * @param length - ilosc elementow w tablicy.
			 * @param dimension - ilosc wymiarow tablicy. 
			 */
			Array(final String type, final int length, final int dimension) {
				this.ID = type + "#ID" + id++ + "/D" + dimension + "/S" + length;
				this.TYPE = type;
				this.size = 0;
				this.dimension = dimension;
			}

			/**
			 * Dodaje dodatkowy wymiar do tablicy.
			 * @param size - ilosc elementow w tablicy.
			 * @return zmienna tablicowa w reprezentacji RDF.
			 */
			Array writeArray(final int size) {
				Array array = new Array(TYPE, size, dimension-1);
				addArray(ID, "_"+this.size++, array.ID);
				return array;
			}

			/**
			 * Dodaje zmienna primitywna do tablicy.
			 * @param value - wartosc zmiennej primitywnej.
			 */
			void writePrimitive(final String value) {
				addPrimitive(ID, TYPE, "_"+size++, value);
			}

			/**
			 * Dodaje zmienna referencyjna do tablicy.
			 * @return zmienna referencyjna w reprezentacji RDF.
			 */
			Reference writeReference() {
				Reference ref = new Reference(TYPE);
				addReference(ID, "_"+size++, "reference", ref.ID);
				return ref;
			}

			/**
			 * Dodaje pusta referencje/wartosc do tablicy.
			 */
			void writeNull() {
				addReference(ID, "_"+size++, "reference", "null");
			}
		}

		/**
		 * Pozwala na zapis kolekcji do strumienia w formacie RDF.
		 * @author Daniel Kur
		 */
		class Collection {
			/**
			 * Unikalny identyfikator dla kolekcji. 
			 */
			private final String ID;
			
			/**
			 * Typ generyczny kolekcji. 
			 */
			private final String GENE;
			
			/**
			 * Licznik ilosci elementow w kolekcji. 
			 */
			private int size = 1;

			/**
			 * Reprezentuje kolekcje w formacie RDF.
			 * @param type - typ kolekcji.
			 * @param generic - typ generyczny. 
			 */
			Collection(final String type, final String generic) {
				this.ID = type + "," + generic + "#ID" + id++;
				this.GENE = generic;
			}

			/**
			 * Dodaje zmienna referencyjna do kolekcji.
			 * @return zmienna referencyjna w reprezentacji RDF.
			 */
			Reference writeReference() {
				Reference ref = new Reference(GENE);
				addReference(ID, "_"+size++, "reference", ref.ID);
				return ref;
			}

			/**
			 * Dodaje zmienna referencyjna opakowywujaca typ primitywny do kolekcji.
			 * @param value - wartosc zmiennej.
			 */
			void writePrimitive(final String value) {
				addPrimitive(ID, GENE, "_"+size++, value);
			}
		}

		/**
		 * Pozwala na zapis map do strumienia w formacie RDF.
		 * @author Daniel Kur
		 */
		class Map {
			/**
			 * Unikalny identyfikator dla mapy. 
			 */
			private final String ID;
			
			/**
			 * Typ klucza mapy. 
			 */
			private final String KEY;
			
			/**
			 * Typ wartosci mapy. 
			 */
			private final String VAL;
			
			/**
			 * Licznik ilosci kluczy w mapie. 
			 */
			private int size = 1;

			/**
			 * Reprezentuje mape w formacie RDF.
			 * @param type - typ mapy.
			 * @param key - typ klucza.
			 * @param value - typ wartosci. 
			 */
			Map(final String type, final String key, final String value) {
				this.ID = type + "," + key + "," + value + "#ID" + id++;
				this.KEY = key;
				this.VAL = value;
			}

			/**
			 * Dodaje zmienna referencyjna do klucza w mapie.
			 * @return zmienna referencyjna w reprezentacji RDF.
			 */
			Reference writeKey() {
				Reference ref = new Reference(KEY);
				addReference(ID, "_"+size, "key", ref.ID);
				return ref;
			}

			/**
			 * Dodaje zmienna referencyjna opakowywujaca typ primitywny do klucza w mapie.
			 * @param value - wartosc zmiennej.
			 */
			void writePrimitiveKey(final String value) {
				addPrimitive(ID, "key", "_"+size, value);
			}

			/**
			 * Dodaje zmienna referencyjna do wartosci w mapie.
			 * @return zmienna referencyjna w reprezentacji RDF.
			 */
			Reference writeValue() {
				Reference ref = new Reference(VAL);
				addReference(ID, "_"+size++, "value", ref.ID);
				return ref;
			}

			/**
			 * Dodaje zmienna referencyjna opakowywujaca typ primitywny do wartosci w mapie.
			 * @param value - wartosc zmiennej.
			 */
			void writePrimitiveValue(final String value) {
				addPrimitive(ID, "value", "_"+size++, value);
			}
		}
	}

	/**
	 * Dodaje do strumienia serializowana instancje klasy w formacie RDF.
	 * @param type - nazwa typu serializowanej klasy.
	 */
	Reference writeClass(final String type) {
		Reference ref = new Reference(type);
		addReference("serialization", type, "object", ref.ID);
		return ref;
	}

	/**
	 * Dodaje do strumienia wskaznik na juz istniejaca referencje w formacie RDF.
	 * @param name - nazwa referencji.
	 * @param identifier - referencja na ktora ma wskazywac.
	 */
	final void writePointer(final String name, final String identifier) {
		addReference("serialization", name, "object", identifier);
	}

	/**
	 * Dodaje do strumienia tekst komentarza w formacie RDF.
	 * @param comment - tresc dodawanego komentarza
	 */
	void writeComment(final String comment) {
		addComment(comment);
	}
	
	/**
	 * Otwiera strumien z serializowanymi obiektami.
	 */
	private final void open() {
		try {
			rdf.startRDF();
		} catch(RDFHandlerException e) {
			LOG.warning("Nie moge otworzyc strumienia danych.");
		}
	}

	/**
	 * Oproznia strumien z serializowanymi obiektami.
	 */
	void flush() {
		try {
			java.lang.reflect.Field f = rdf.getClass().getDeclaredField("writer");
			f.setAccessible(true);
			java.lang.reflect.Method m = f.get(rdf).getClass().getDeclaredMethod("flush");
			m.setAccessible(true);
			m.invoke(f.get(rdf),(Object[])null);
		} catch(Exception e) {
			LOG.warning("Nie moge oproznic strumienia danych.");
		}
	}

	/**
	 * Zamyka strumien z serializowanymi obiektami.
	 */
	void close() {
		try {
			rdf.endRDF();
		} catch(RDFHandlerException e) {
			LOG.warning("Nie moge zamknac strumienia danych.");
		}
	}
}
